#include <iostream>
#include <string.h>
#include <pthread.h>
#include <sys/time.h>
#include "netmgt.h"

// Forward declares
//
void usage();
void runcmd(const short threads, const char* cmd, CLIENT *clnt, bool isMuttering);
void* runcmd_func(void* args);

// Package of arguments into threading function
//
struct runcmd_args
{
	const char* cmd;
	const char* hostname;
	bool mutter;

	// Simple init via constructor
	//
	runcmd_args() :
	cmd(NULL),
		mutter(true)
	{
	}
};

// Used to return time info to thread parent
//
struct TimeInfo
{
	timeval startTime, endTime;
};


// Run the command on the server and display output
//
void* runcmd_func(void* vpargs)
{
	char **buffer;
	runcmd_args* args = (runcmd_args*) vpargs;
	TimeInfo* pTimeInfo = new TimeInfo();
	CLIENT* client = clnt_create(args->hostname, RPC_PRG, RPC_VER, "tcp");

	if(client == NULL){
		clnt_pcreateerror(args->hostname);
		exit(1);
	}

	// Get starting time
	//
	gettimeofday(&pTimeInfo->startTime, NULL);
	if( buffer = runcmd_1(const_cast<char**>(&(args->cmd)), client)){
		if (args->mutter)
		{
			std::cout << *buffer << "\n";
		}
	}
	else 
	{
		std::cout << "Error: Unable to execute \"" << args->cmd << "\"\n";
	}

	// Get time now that all threads have joined back together
	//
	gettimeofday(&pTimeInfo->endTime, NULL); 

	clnt_destroy(client);
	pthread_exit(pTimeInfo);
}

// Run the command on the server and display output
//
void runcmd(const short threads, const char* cmd, const char* hostname, bool isMuttering)
{
	// Time tracking variables
	//
	long timeLapse_sec = 0L;
	long timeLapse_usec = 0L;

	// Thread id array
	//
	pthread_t thread[threads];

	// Arguments for thread function
	//
	runcmd_args args;
	args.cmd = cmd;
	args.mutter = isMuttering;
	args.hostname = hostname;

	// Run the command in each thread
	//
	for (short i = 0; i < threads; ++i)
	{
		int ret = pthread_create(&(thread[i]), NULL, runcmd_func, (void*) &args);
		if (ret != 0)
		{
			std::cout << "Error: Unable to create thread " << i << ".\n";
		}
	}

	// Wait on all the threads to complete
	//
	for (short i = 0; i < threads; ++i)
	{
		TimeInfo* pTime = NULL;

		int ret = pthread_join(thread[i], (void**) &pTime);
		if (ret != 0)
		{
			std::cout << "Error: Unable to wait on thread " << i << ".\n";
		}
		else 
		{
			// Calculate time lapse accounting for all cases
			//
			if (pTime->endTime.tv_sec > pTime->startTime.tv_sec)
			{
				timeLapse_sec += pTime->endTime.tv_sec - pTime->startTime.tv_sec;
			}


			if (pTime->endTime.tv_usec > pTime->startTime.tv_usec)
			{
				timeLapse_usec += pTime->endTime.tv_usec - pTime->startTime.tv_usec;
			}
			else if (pTime->endTime.tv_usec < pTime->startTime.tv_usec)
			{
				timeLapse_usec += pTime->endTime.tv_usec + (1000000 - pTime->startTime.tv_usec);
				timeLapse_sec--;
			}
		}

		// Delete time info
		//
		delete pTime;
	}

	timeLapse_sec /= threads;
	timeLapse_usec  /= threads;


	// Display start time, end time, and time lapse
	std::cout << "Time Lapse: " << timeLapse_sec << " seconds and " << (timeLapse_usec /1000) << " milliseconds\n";
	std::cout << '\n';
}

// Display program usage
//
void usage()
{
	std::cout << "client [options]\n";
	std::cout << "Options:\n";
	std::cout << "  --help\t\tDisplay usage information\n";
	std::cout << "  -h [hostname]\t\tServer name; default local machine\n";
	std::cout << "  -t [threads]\t\tNumber of simultanous clients; default 1\n";
	std::cout << "  -m \t\t\tDisables muttering of incoming data\n";

	exit(0);
}

// Client entry point
//
int main (int argc, char *argv[]) 
{
	char  hostname[MAXHOSTNAMELEN];
	short threads = 1;
	bool isMuttering = true;

	// If no parameters are passed in then display usage and quit
	//
	if (argc  < 2)
	{
		usage();
	}

	// Process arguments to set the host name and port
	//
	for (int i = 1; i < argc; ++i)
	{
		if (strcmp("--help", argv[i]) == 0)
		{
			usage();
		}
		else if (strcmp("-h", argv[i]) == 0 && i + 1 < argc)
		{
			++i;    // extra step
			strcpy(hostname, argv[i]);
		}
		else if (strcmp("-t", argv[i]) == 0 && i + 1 < argc)
		{
			++i;    // extra step
			threads = (short) atoi(argv[i]);
		}
		else if (strcmp("-m", argv[i]) == 0)
		{
			isMuttering = false;
		}
		else 
		{
			// Invalid parameters
			//
			usage();
		}
	}

	// Loop menu until user selects to quit
	//
	bool isDone = false;
	while (!isDone)
	{
		char input = '\0';

		std::cout << "Hostname:\t" << hostname << '\n';
		std::cout << "Threads:\t" << threads << "\n\n";

		std::cout << "1. Host current Date and Time\n"
			"2. Host uptime\n"
			"3. Host memory use\n"
			"4. Host Netstat\n"
			"5. Host current users\n"
			"6. Host running processes\n"
			"7. Quit\n\n";

		std::cout << "Enter the corresponding number and press <ENTER>: ";   
		std::cin >> input;

		std::cout << "\n";
		switch (input)
		{
		case '1':
			runcmd(threads, "date",  hostname, isMuttering);
			break;
		case '2':
			runcmd(threads, "uptime", hostname, isMuttering);
			break;
		case '3':
			runcmd(threads, "free", hostname, isMuttering);
			break;
		case '4':
			runcmd(threads, "netstat", hostname, isMuttering);
			break;
		case '5':
			runcmd(threads, "who", hostname, isMuttering);
			break;
		case '6':
			runcmd(threads, "ps -e", hostname, isMuttering);
			break;
		case '7':
			isDone = true;
			break;
		default:
			std::cout << "Error: Invalid command, try again.\n\n";
			break;
		}
	}    

	return 0;
}
